#!/usr/bin/env python

import logging
import logging.handlers
import optparse
import os
import pexpect
import sys
import testhelp
import unittest

from qnaire import *

KEEP = False

# ---------------------------------------------------------------------------
def main(args):
    p = optparse.OptionParser()
    p.add_option('-S', '--stdout',
                 action='store_true', default=False, dest='stdout',
                 help='show stdout of tests')
    p.add_option('-d', '--debug',
                 action='store_true', default=False, dest='debug',
                 help='run under the debugger')
    p.add_option('-k', '--keep',
                 action='store_true', default=False, dest='keep',
                 help='keep test files')
    p.add_option('-l', '--list',
                 action='store_true', default=False, dest='list',
                 help='list tests')
    p.add_option('-q', '--quiet',
                 action='store_true', default=False, dest='quiet',
                 help='quieter')
    p.add_option('-t', '--to',
                 action='store', default='', dest='final',
                 help='run all tests up to this one')
    p.add_option('-v', '--verbose',
                 action='store_true', default=False, dest='verbose',
                 help='louder')
    (o, a) = p.parse_args(args)

    global KEEP

    if o.debug:
        pdb.set_trace()

    show_stdout(o.stdout)

    if o.keep:
        KEEP = True
        
    setup_logging()
    
    for opt in ['-d', '--debug', '-S', '--stdout']:
        if opt in args:
            args.remove(opt)
            
    testhelp.main(args, 'QTest')

# ---------------------------------------------------------------------------
class QSpawn(pexpect.spawn):
    """
    Here we subclass pexpect.spawn so we can do two things:

    1) check the value of show_stdout to decide how to set self.logfile,

    2) override expect() and add a check for Tracebacks each time
       expect() runs. This allows us to avoid sticking
       "assert('Traceback' not in S.before)" inline after every call
       to S.expect()

    Note that there are occasions when a traceback is expected as part
    of a test's successful output and we don't EzpubSpawn.expect to
    complain about it. For those cases, we've added catch_tb so we can
    tell expect NOT to catch the traceback.
    """
    def __init__(self, cmd, show_stdout):
        super(QSpawn, self).__init__(cmd)
        if show_stdout:
            self.logfile = sys.stdout
        else:
            self.logfile = None

    def expect(self, pattern, catch_tb=True, timeout=-1,
               searchwindowsize=None):
        rc = super(QSpawn, self).expect(pattern, timeout, searchwindowsize)
        if "Traceback" in self.before and catch_tb:
            raise AssertionError("Traceback detected")
        return rc
    
                                                                            
# ---------------------------------------------------------------------------
class QTest(unittest.TestCase):
    
    # -----------------------------------------------------------------------
    def test_comma_list(self):
        """
        Tests for routine comma_list().
        """
        try:
            cl = comma_list("one, two, three")
            self.assertEqual(cl, ["one", "two", "three"])

            cl = comma_list("1, 'seven', Billy, \"Jack\"")
            self.assertEqual(cl, ['1', 'seven', 'Billy', 'Jack'])

            cl = comma_list("single item")
            self.assertEqual(cl, ['single item'])

            cl = comma_list('Sally Ann, "  leading whitespace", Ruby Lee   ')
            self.assertEqual(cl, ['Sally Ann',
                                  '  leading whitespace',
                                  'Ruby Lee'])
        except:
            tlogger.info('%s * * * FAILED' % my_name())
            raise
        tlogger.info('%-30s PASSED' % my_name())

    # -----------------------------------------------------------------------
    def test_which_member(self):
        """
        Tests for routine which_member().
        """
        try:
            rc = which_member(['epsilon', 'delta', 'gimel'], 'theta')
            self.assertEqual(rc, -1)

            rc = which_member(['epsilon', 'delta', 'gimel'], 'epsilon')
            self.assertEqual(rc, 0)

            rc = which_member(['epsilon', 'delta', 'gimel'], 'del')
            self.assertEqual(rc, 1)

            rc = which_member(['epsilon eta', 'delta zelphoid', 'gimel'], 'a zel')
            self.assertEqual(rc, 1)

            rc = which_member(['epsilon', 'delta', 'gimel'], 'gime')
            self.assertEqual(rc, 2)

            rc = which_member(['epsilon', 'delta', 'gimel'], 'gimelda')
            self.assertEqual(rc, -1)

        except:
            tlogger.info('%s * * * FAILED' % my_name())
            raise
        tlogger.info('%-30s PASSED' % my_name())

    # -----------------------------------------------------------------------
    def test_constructor(self):
        """
        Test the Questionnaire constructor.
        """
        try:
            filename = 'qtest.qn'
            f = open(filename, 'w')
            f.writelines(['[001]\n',
                          '\n',
                          'question = What is up?\n',
                          'type     = text\n',
                          '\n',
                          '[002]\n',
                          'question = Nothing much. You?\n',
                          'type     = choice\n',
                          'choices  = yes, no\n'])
            f.close()
            q = Questionnaire('qtest.qn')
            s = q.cf.sections()
            s.sort()
            self.assertEqual(s, ['001', '002'])
            self.assertEqual(q.cf.get('001', 'type'), 'text')
            self.assertEqual(q.cf.get('002', 'choices'), 'yes, no')

            if not KEEP:
                os.unlink(filename)
            
        except:
            tlogger.info('%s * * * FAILED' % my_name())
            raise
        tlogger.info('%-30s PASSED' % my_name())

    # -----------------------------------------------------------------------
    def test_required_false_default(self):
        """
        'required' is false by default.
        """
        try:
            filename = 'qtest.qn'
            f = open(filename, 'w')
            f.writelines(['[001]\n',
                          '\n',
                          'question = What is up?\n',
                          'type     = text\n',
                          '\n',
                          '[002]\n',
                          'question = Nothing much. You?\n',
                          'type     = choice\n',
                          'choices  = yes, no\n',
                          '[003]\n',
                          'question = You must answer this.\n',
                          'type     = text\n',
                          'required = true\n',
                          '[004]\n',
                          'question = Okay, all done.\n',
                          'type     = stop\n',
                          ])
            f.close()
            q = Questionnaire('qtest.qn')
            A = q.question('001')
            B = q.question('002')
            C = q.question('003')
            D = q.question('004')
            self.assertEqual(A['required'], False)
            self.assertEqual(B['required'], None)
            self.assertEqual(C['required'], True)
            self.assertEqual(D['required'], None)

            if not KEEP:
                os.unlink(filename)
            
        except:
            tlogger.info('%s * * * FAILED' % my_name())
            raise
        tlogger.info('%-30s PASSED' % my_name())
            
    # -----------------------------------------------------------------------
    def test_writes_output_file(self):
        """
        Verify that qnaire.py writes answers to an output file.
        """
        try:
            infile = 'qtest.qn'
            outfile = 'qnaire.out'
            
            f = open(infile, 'w')
            f.writelines(['[001]\n',
                          '\n',
                          'question = What is up?\n',
                          'type     = text\n',
                          ])
            f.close()

            S = pexpect.spawn("./qnaire.py %s" % infile)
            S.expect("> ")
            S.sendline("Not much")
            S.expect("Please review")
            S.expect("> ")
            S.sendline("yes")
            S.expect(pexpect.EOF)

            self.assertEqual(os.path.exists(outfile), True)
            c = contents(outfile)
            self.assertEqual("answer = Not much" in c, True)

            if not KEEP:
                os.unlink(infile)
                os.unlink(outfile)
            
        except:
            tlogger.info('%s * * * FAILED' % my_name())
            raise
        tlogger.info('%-30s PASSED' % my_name())
            
    # -----------------------------------------------------------------------
    def test_name_output_file(self):
        """
        Verify that qnaire.py writes answers to an output file that we can name.
        """
        try:
            infile = 'qtest.qn'
            outfile = 'qtest.answers'
            
            f = open(infile, 'w')
            f.writelines(['[001]\n',
                          '\n',
                          'question = What is up?\n',
                          'type     = text\n',
                          ])
            f.close()

            S = pexpect.spawn("./qnaire.py %s -o %s" % (infile, outfile))
            S.expect("> ")
            S.sendline("Not much")
            S.expect("Please review")
            S.expect("> ")
            S.sendline("yes")
            S.expect(pexpect.EOF)

            self.assertEqual(os.path.exists(outfile), True)
            c = contents(outfile)
            self.assertEqual("answer = Not much" in c, True)

            if not KEEP:
                os.unlink(infile)
                os.unlink(outfile)
                
        except:
            tlogger.info('%s * * * FAILED' % my_name())
            raise
        tlogger.info('%-30s PASSED' % my_name())
            
    # -----------------------------------------------------------------------
    def test_question_order(self):
        """
        Verify questions are asked in proper order.
        """
        try:
            infile = 'order.qn'
            outfile = 'order.out'
            
            f = open(infile, 'w')
            f.writelines(['[001]\n',
                          'question = What is up?\n',
                          'type     = text\n',
                          '[002]\n',
                          'question = second question\n',
                          'type     = text\n',
                          '[003]\n',
                          'question = third question\n',
                          'type     = text\n',
                          ])
            f.close()

            S = pexpect.spawn("./qnaire.py %s -o %s" % (infile, outfile))
            S.expect("> ")
            self.assertEqual('What is up?' in S.before, True)
            S.sendline("Not much")
            
            S.expect("> ")
            self.assertEqual('second question' in S.before, True)
            S.sendline('second answer')

            S.expect("> ")
            self.assertEqual('third question' in S.before, True)
            S.sendline('third answer')
            
            S.expect("Please review")
            S.expect("> ")
            S.sendline("yes")
            S.expect(pexpect.EOF)

            self.assertEqual(os.path.exists(outfile), True)
            c = contents(outfile)
            self.assertEqual("answer = Not much" in c, True)

            s = [x for x in c.split('\n') if x.startswith('[')]
            y = []
            y.extend(s)
            y.sort()
            self.assertEqual(s, y)
            
            if not KEEP:
                os.unlink(infile)
                os.unlink(outfile)
            
        except:
            tlogger.info('%s * * * FAILED' % my_name())
            raise
        tlogger.info('%-30s PASSED' % my_name())
            
# ---------------------------------------------------------------------------
def my_name():
    return sys._getframe(1).f_code.co_name

# ---------------------------------------------------------------------------
def setup_logging():
    global tlogger

    tlogger = logging.getLogger('Questionnaire')
    tlogger.setLevel(logging.INFO)

    hostname = socket.gethostname().split('.')[0]
    fh = logging.handlers.RotatingFileHandler("./qtest.log",
                                              maxBytes=10*1024*1024,
                                              backupCount=5)
    strfmt = "%" + "(asctime)s [%s] " % hostname + "%" + "(message)s"
    fmt = logging.Formatter(strfmt, datefmt="%Y.%m%d %H:%M:%S")
    fh.setFormatter(fmt)

    tlogger.addHandler(fh)
    tlogger.info('-' * 50)
                                
# ---------------------------------------------------------------------------
def show_stdout(value=None):
    """
    Return value of global value show_stdout. Optionally set it if
    value is specified. If it is not set, the default return value is False.
    """
    global show_output
    try:
        rval = show_output
    except:
        rval = False
            
    if value != None:
        show_output = value
                
    return rval

                                                        
# ---------------------------------------------------------------------------
if __name__ == '__main__':
    main(sys.argv)
